package com.dc.juc.example._11_lock;

import java.util.concurrent.locks.StampedLock;

/**
 * Description: StampedLock 案例
 * @author: gdc
 * @date: 2021/8/17
 * @version 1.0
 */
public class _4_StampedLock {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        /**
         * 悲观写锁案例，
         */
        public void move(double deltaX, double deltaY) {
            // 获取排他写锁
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                // 释放写锁
                sl.unlockWrite(stamp);
            }
        }

        /**
         * 乐观读锁案例
         *
         *  注意：乐观读锁在保证数据一致性上需要拷贝一份要操作的变量到方法栈，并且在操作数据时候可能其他写线程已经修改了数据，
         *  而我们操作的是方法栈里面的数据，也就是一个快照，所以最多返回的不是最新的数据，但是一致性还是得到保障的。
         *
         *  对于乐观读（如果没有进入写模式）可以减少一次读锁的性能消耗，并且不会阻塞写入的操作
         *  乐观读遇到写后转化为悲观，相当于滞后一步
         */
        public double distanceFromOrigin() {
            // 获得一个乐观读锁
            long stamp = sl.tryOptimisticRead();

            // 将两个字段读入本地局部变量
            double currentX = x, currentY = y;

            // 检查发出乐观读锁后同时是否有其他写锁发生？
            if (!sl.validate(stamp)) {
                // 已经发生写锁，只能再次获得一个读悲观锁
                stamp = sl.readLock();
                try {
                    // 将两个字段重新读入本地局部变量
                    currentX = x;
                    currentY = y;
                } finally {
                    // 释放悲观读锁
                    sl.unlockRead(stamp);
                }
            }

            // 走到这里，如果没有其他写锁产生，那么可以不加读锁，减少了读锁的开销
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        /**
         * 悲观读锁案例
         */
        public void moveIfAtOrigin(double newX, double newY) {
            // 获取悲观读锁
            long stamp = sl.readLock();

            try {
                while (x == 0.0 && y == 0.0) {      //循环，检查当前状态是否符合
                    long ws = sl.tryConvertToWriteLock(stamp);  //将读锁转为写锁
                    if (ws != 0L) {                 //这是确认转为写锁是否成功
                        stamp = ws;                 //如果成功 替换票据
                        x = newX;                   //进行状态改变
                        y = newY;                   //进行状态改变
                        break;
                    } else {                        //如果不能成功转换为写锁
                        sl.unlockRead(stamp);       //我们显式释放读锁
                        stamp = sl.writeLock();     //显式直接进行写锁 然后再通过循环再试
                    }
                }
            } finally {
                //释放读锁或写锁
                sl.unlock(stamp);
            }
        }
    }
}
